package com.gutsyzhan.bilibili.websocket;

import com.alibaba.fastjson.JSONObject;
import com.gutsyzhan.bilibili.domain.DanMu;
import com.gutsyzhan.bilibili.service.DanMuService;
import com.gutsyzhan.bilibili.util.TokenUtil;
import com.mysql.jdbc.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

@Component
@ServerEndpoint("/imserver/{token}")
public class WebSocketService {

    private static final Logger logger = LoggerFactory.getLogger(WebSocketService.class);

    private static final AtomicInteger ONLINE_COUNT = new AtomicInteger(0);

    /**
     * 构建key为sessionId，value为WebSocketService的map
     */
    private static final ConcurrentHashMap<String, WebSocketService> WEBSOCKET_MAP = new ConcurrentHashMap<>();

    private Session session;

    private String sessionId;

    /**
     * 通过解析token来获取用户id
     */
    private Long userId;

    /**
     * 定义一个全局的 ApplicationContext 对象
     */
    private static ApplicationContext APPLICATION_CONTEXT;

    public static void setApplicationContext(ApplicationContext applicationContext){
        WebSocketService.APPLICATION_CONTEXT = applicationContext;
    }

    /**
     * onOpen注解用于标识连接建立成功时应当调用的方法
     */
    @OnOpen
    public void openConnection(Session session, @PathParam("token") String token){
        try {
            //获取登录用户的id
            this.userId = TokenUtil.verifyToken(token);
        }catch (Exception e){}
        this.sessionId = session.getId();
        this.session = session;
        //连接成功后，判断webSocketMap中是否存在该Session，如果存在则去掉之前的并加入新的Session，不存在则直接加入并更新在线人数
        if(WEBSOCKET_MAP.containsKey(sessionId)){
            WEBSOCKET_MAP.remove(sessionId);
            WEBSOCKET_MAP.put(sessionId, this);
        }else{
            WEBSOCKET_MAP.put(sessionId, this);
            ONLINE_COUNT.getAndIncrement();
        }
        logger.info("用户连接成功：" + sessionId + "，当前在线人数为：" + ONLINE_COUNT.get());
        //连接成功后，需要通知前端
        try{
            this.sendMessage("0");
        }catch (Exception e){
            logger.error("连接异常");
        }
    }

    /**
     * onClose注解用于标识关闭连接时应当调用的方法
     */
    @OnClose
    public void closeConnection(){
        if(WEBSOCKET_MAP.containsKey(sessionId)){
            WEBSOCKET_MAP.remove(sessionId);
            ONLINE_COUNT.getAndDecrement();
        }
        logger.info("用户退出：" + sessionId + "，当前在线人数为：" + ONLINE_COUNT.get());
    }

    /**
     * OnMessage注解用于标识当前端往后端发送消息时，调用的方法
     */
    @OnMessage
    public void OnMessage(String message){
        logger.info("用户信息：" + sessionId + "，报文：" + message);
        if(!StringUtils.isNullOrEmpty(message)){
            try{
                //群发消息
                for (Map.Entry<String, WebSocketService> entry: WEBSOCKET_MAP.entrySet()){
                    WebSocketService webSocketService = entry.getValue();
                    if(webSocketService.session.isOpen()){
                        webSocketService.sendMessage(message);
                    }
                }
                //补充弹幕信息
                if(this.userId != null){
                    DanMu danMu = JSONObject.parseObject(message, DanMu.class);
                    danMu.setUserId(userId);
                    danMu.setCreateTime(new Date());
                    DanMuService danMuService = (DanMuService)APPLICATION_CONTEXT.getBean("danMuService");
                    //将弹幕持久化到数据库
                    danMuService.addDanMu(danMu);
                    //将弹幕持久化到Redis
                    danMuService.addDanMuToRedis(danMu);
                }
            }catch (Exception e){
                logger.error("弹幕接收出现问题");
                e.printStackTrace();
            }
        }
    }

    /**
     * OnError注解用于标识当消息发生过程中出现异常的时候，调用的方法
     */
    @OnError
    public void OnError(Throwable error){
    }

    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }
}
